# -*- coding: utf-8 -*-
# Created by liangfuting on 2020/7/9
# Copyright (c) 2020 liangfuting. All rights reserved.
from types import FunctionType
from functools import wraps

from django import forms
from django.db.models import Q,ForeignKey,ManyToManyField
from django.http import QueryDict
from django.urls import path,re_path,include
from django.shortcuts import HttpResponse,render,redirect,reverse,get_list_or_404,get_object_or_404
from django.utils.safestring import mark_safe
from stark.utils.pagination import Pagination

def get_choice_text(title,field):
    #对于startk定制列中，choice列如果想显示中文，调用此方法即可
    def inner(self,obj=None,is_header=None):
        if is_header:
            return title
        method="get_{}_display".format(field)
        return getattr(obj,method)()
    return inner

def get_datetime_text(title,field,format='%Y-%m-%d'):
    # 对于startk定制时间列
    def inner(self, obj=None, is_header=None):
        if is_header:
            return title
        datetime_value=getattr(obj,field)
        # print(type(datetime_value))
        return datetime_value.strftime(format)
    return inner

def get_manytomany(title,field):
    # 对于startk定制多对多列的显示
    def inner(self, obj=None, is_header=None):
        if is_header:
            return title
        queryset=getattr(obj,field).all()
        # print(type(datetime_value))
        return ','.join([str(row)for row in queryset])
    return inner

class BootStrapModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(BootStrapModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'
            field.widget.attrs['placeholder'] = field.label


class SearchOption:
    def __init__(self,field,is_multi=False,db_condition=None,text_func=None,value_func=None):
        """

        :param field:组合搜索关联的字段
        :param db_condition:数据库关联查询时的条件
        :param text_func:此函数用于显示组合搜索按键页面文本,如果定义了，就按照自定义的进行显示
        :param value_func:此函数用于显示组合搜索按键页面的值,如果定义了，就按照自定义的进行显示
        :param is_multi：是否支持多选
        """
        self.field = field
        if not db_condition:
            db_condition= {}
        self.db_condition=db_condition
        self.text_func=text_func
        self.value_func=value_func
        self.is_choice=False
        self.is_multi=is_multi

    def get_db_condition(self,request,*args,**kwargs):
        return self.db_condition

    def get_queryset_or_tuple(self,model_class,request,*args,**kwargs):
        field_object = model_class._meta.get_field(self.field)
        title=field_object.verbose_name
        # print(type(field_object), type(self.field))

        # 获取关联数据。
        if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
            # 针对外键或多对多，去获取表中的数据
            db_condition= self.get_db_condition(request,*args,*kwargs)
            # print(db_condition)
            #返回queryset
            return SearchGroupRow(title,field_object.related_model.objects.filter(**db_condition),self,request.GET)
        else:
            #返回field和元组,然后进行统一的封装
            self.is_choice = True
            return SearchGroupRow(title,field_object.choices,self,request.GET)

    def get_text(self,field_object):
        """
        获取文本的函数
        :param field_object:SearchGroupRow中self.get_queryset_or_tuple中每个对象的值
        :return:
        """
        if self.text_func:
            return self.text_func(field_object)
        if self.is_choice:
            return field_object[1]
        # 如果是query_set,对象的属性值
        return str(field_object)

    def get_value(self,field_object):
        if self.value_func:
            return self.value_func(field_object)
        if self.is_choice:
            return field_object[0]
        #如果是query_set,返回主键
        return field_object.pk


class SearchGroupRow:
    def __init__(self,title,get_queryset_or_tuple,search_option,query_dict):
        """

        :param title: 组合搜索的列名称
        :param get_queryset_or_tuple:组合搜索获取到的数据
        :param search_option: 配置
        :param query_dict: request.GET
        """
        self.title=title
        self.get_queryset_or_tuple = get_queryset_or_tuple
        self.search_option=search_option
        self.query_dict=query_dict

    def __iter__(self):
        # 如果 一个类定义了__iter__方法，且该方法返回一个迭代器，那么就称这个类的实例为可迭代对象，此对象可循环
        # return iter(self.get_queryset_or_tuple)
        yield '<div class="whole">'
        yield self.title+":"#表头
        yield '</div>'


        yield '<div class="others">'

        total_query_dict=self.query_dict.copy()
        total_query_dict._mutable=True
        origin_value_list = self.query_dict.getlist(self.search_option.field)
        if not origin_value_list:
            #如果值不存在，拿到参数，重新生成下
            yield "<a href='?{}' class='active'>全部</a>".format(total_query_dict.urlencode())
        else:
            #如果值存在就去掉，重新生成url。
            total_query_dict.pop(self.search_option.field)
            yield "<a href='?{}' >全部</a>".format(total_query_dict.urlencode())


        for item in self.get_queryset_or_tuple:
            text=self.search_option.get_text(item)
            value=str(self.search_option.get_value(item))
            #request.GET gender=1&depart=2 ==> QueryDict={gender:["1",],depart:["2",]}
            #获取组合搜索按键文本背后对应的值
            query_dict=self.query_dict.copy()
            # query_dict QueryDict={gender:["1",],depart:["2",]}
            query_dict._mutable=True
            #获取原始的值

            # print(origin_value_list)
            if not self.search_option.is_multi:
                #如果是单选
                query_dict[self.search_option.field] = value
                if value in origin_value_list:
                    #如果在原始值中,超连接会加重显示，并从query_dict中出掉。
                    query_dict.pop(self.search_option.field)

                    yield "<a href='?{}' class='active'>{}</a>".format(query_dict.urlencode(),text)

                else:
                    yield "<a href='?{}'>{}</a>".format(query_dict.urlencode(), text)
            else:
                #{'gender',['1',]}
                multi_value_list=query_dict.getlist(self.search_option.field)
                if value in multi_value_list:
                    #已经存在的话，需要移除掉
                    multi_value_list.remove(value)
                    query_dict.setlist(self.search_option.field,multi_value_list)
                    yield "<a href='?{}' class='active'>{}</a>".format(query_dict.urlencode(), text)
                else:
                    multi_value_list.append(value)
                    query_dict.setlist(self.search_option.field, multi_value_list)
                    yield "<a href='?{}' >{}</a>".format(query_dict.urlencode(), text)



            # print(text,value,self.query_dict)

        yield '</div>'



class StarkHandler:
    list_display = []
    #默认的分页大小，子类可自定义分页大小
    per_page_count = 20
    #通过下面的属性定义是否有添加功能
    has_add_btn = True
    model_form_class = None
    order_list = []
    search_list = []#默认不展示
    action_list= []
    search_group = []#组合搜索列表


    def __init__(self,site, model_class,prev):
        self.model_class = model_class
        self.model_name = self.model_class._meta.model_name
        self.app_label = self.model_class._meta.app_label
        self.verpose_name = self.model_class._meta.verbose_name
        self.prev = prev
        self.site=site
        self.request=None
        # self.per_page_count=None
        # self.per_page_count=2
        # print(self.model_name)

    def multi_delete(self,request,*args,**kwargs):
        '''
        批量删除的函数
        :return:
        '''
        pk_list = request.POST.getlist("pk")
        self.model_class.objects.filter(id__in=pk_list).delete()
        #函数执行完成后，如果有返回值 ，则显示 返回值 ，如果 没有返回值 ，则跳转到之前的页面

    multi_delete.text='批量删除'

    def multi_init(self,request,*args,**kwargs):
        '''
        批量初始化的函数
        :return:
        '''
        pass
    multi_init.text='批量初始化'

    action_list.extend([multi_delete,])  # 放批量操作的函数

    def get_action_list(self):
        # for item in self.action_list:
        #     print(item.text)
        return self.action_list

    def get_search_group(self):
        return self.search_group

    def get_search_group_condition(self,request):
        #组合搜索的条件
        condition={}
        for option in self.get_search_group():
            if option.is_multi:
            #getlist支持多选
                value_list=request.GET.getlist(option.field)
                if not value_list:
                    continue
                condition["{}__in".format(option.field)]=value_list #tags=[1,2]
            else:
                value=request.GET.get(option.field)
                if not value:
                    continue
                condition[option.field]=value #tags=1
        return condition


    def display_check(self,obj=None,is_header=None):
        if is_header:
            return '选择'
        # esle:
        #     return "<a href='{}'>编辑</a>".format(self.get_edit_url_name,obj.pk)
        # name='{}:{}'.format(self.site.namespace,self.get_edit_url_name)
        # print(name)
        # url=self.reverse_change_url(pk=obj.pk)
        # print(url)
        return mark_safe("<input type='checkbox' name='pk' value='{}'/>".format(obj.pk))

    def display_edit(self,obj=None,is_header=None):
        if is_header:
            return '编辑'
        # esle:
        #     return "<a href='{}'>编辑</a>".format(self.get_edit_url_name,obj.pk)
        # name='{}:{}'.format(self.site.namespace,self.get_edit_url_name)
        # print(name)
        url=self.reverse_change_url(pk=obj.pk)
        # print(url)
        return mark_safe("<a href='{}'>编辑</a>".format(url))

    def display_del(self,obj=None,is_header=None):
        if is_header:
            return '删除'

        url = self.reverse_del_url(pk=obj.pk)
        return mark_safe("<a href='{}'>删除</a>".format(url))

    def display_edit_del(self, obj=None, is_header=None):
        if is_header:
            return '操作'
        edit_url = self.reverse_change_url(pk=obj.pk)
        del_url = self.reverse_del_url(pk=obj.pk)
        return mark_safe(
            "<a href='{}'><i class='fa fa-edit' aria-hidden='true'></i> 编辑</a> <a href='{}'><i class='fa fa-trash-o'></i> 删除</a>".format(
                edit_url, del_url))

    def get_list_display(self):
        '''
        获取页面上应该显示的列,预留页面的钩子方法，子类可继承方法，进行扩展
        :return:
        '''
        value = []
        value.extend(self.list_display)
        value.append(StarkHandler.display_edit_del)
        value.insert(0,StarkHandler.display_check)
        # print(value)
        return value

    def get_order_list(self):
        """
        生成排序列表,默认以id进行降序排序
        :return:
        """
        # if self.order_list:
        #     return self.order_list
        # return ['-id',]
        return self.order_list or ['-id',]

    def get_search_list(self):
        #return search list ,预留钩子方法，便于继承和修改
        return self.search_list


    def list_view(self, request,*args,**kwargs):
        # self.request = request

        """

        :param request:
        :return:
        """
        #1.处理批量的Action
        action_list = self.get_action_list()
        action_dict = { func.__name__:func.text for func in action_list }#{"multi_delete":"批量删除","multi_init":"批量初始化"}
        # print(action_dict)



        #2.处理关键字查询
        #获取模糊查询的列,如果为空，则不显示搜索框，
        search_list = self.get_search_list()

        search_value = request.GET.get('q','')
        #Q用于构造复杂的查询条件，
        conn =Q()
        conn.connector = 'OR'
        # conn.children.append(('name__contains','理解'))
        # conn.children.append(('email', 'alex@cnpiec.com'))
        # conn.children.append(('id__gt', 5))
        if search_value:
            for item in search_list:
                conn.children.append(("{}__icontains".format(item),search_value))



        #3.获取排序规则
        order_list = self.get_order_list()
        #获取组合搜索
        search_group_condition=self.get_search_group_condition(request)
        query_set = self.model_class.objects.filter(conn).filter(**search_group_condition).order_by(*order_list)


        #首先获取page页码，根据每面显示的数据量20，计算出数据库的索引位置。

        #4.处理分页
        # object_list = get_list_or_404(self.model_class)
        all_count=query_set.count()
        query_params=request.GET.copy()#此时query_params不可修改，需要添加_mutable
        query_params._mutable=True



        pager = Pagination(
            current_page=request.GET.get('page'), all_count=all_count, base_url=request.path_info,
            query_params=query_params,per_page=self.per_page_count
        )
        data_list=query_set[pager.start:pager.end]
        # print(data_list)
        # print(reverse("stark:app02_host_list"))
        #处理表头,用户没有定义表头的话，只显示一列
        #self.model_class._meta.get_field('name').verbose_name
        list_display=self.get_list_display()
        #5.表头的处理
        # print(list_display)
        head_list = []
        if list_display:
            for key_or_func in list_display:
                if isinstance(key_or_func,FunctionType):#是否是一个函数
                    verbose_name=key_or_func(self,obj=None,is_header=True)
                else:
                    verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
                head_list.append(verbose_name)
        if len(list_display)<=2:
            head_list.insert(1,self.model_class._meta.model_name)
        # print(head_list)
        #6.处理表的内容

        body_list =[]
        for row in data_list:
            tr_list =[]
            if list_display:
                for key_or_func in list_display:
                    if isinstance(key_or_func,FunctionType):
                        tr_list.append(key_or_func(self,obj=row,is_header=False))
                    else:
                        tr_list.append(getattr(row,str(key_or_func)))
            if len(list_display) <= 2:
                tr_list.insert(1,row)
            body_list.append(tr_list)

        #7.添加功能的处理
        add_btn=self.get_add_btn()

        #8.批量处理的功能
        if request.method == 'POST':
            action_func_name = request.POST.get('action')
            if action_func_name and action_func_name in action_dict:
                action_response = getattr(self,action_func_name)(request,*args,**kwargs)
                # print(func)
                if action_response:
                    return action_response
                else:
                    return redirect(self.reverse_list_url())

        #9。组合搜索的功能
        search_group= self.get_search_group()
        # print(search_group)
        search_group_row_list=[]
        # print(search_group)
        for option_object in search_group:
            #根据gender or depart字符串，去自己对应的model类中找到字段对象，
            row = option_object.get_queryset_or_tuple(self.model_class,request,*args,**kwargs)#传递更多参数，以便处理更多功能
            # print(option_object.field)
            search_group_row_list.append(row)

        context = {
            'object_list': body_list,
            'head_list': head_list,
            'verpose_name': self.verpose_name,
            'pager': pager,
            'add_btn': add_btn,
            'search_list': search_list,
            'search_value': search_value,
            'action_dict': action_dict,
            'search_group_row_list': search_group_row_list,
        }
        # 使用模板时，如果最外层的，则用最外层的，如果外面没有，则用app下面的
        return render(request, 'stark/list.html', context)

    def reverse_common(self,url_name,*args,**kwargs):
        name='{}:{}'.format(self.site.namespace, url_name)
        base_url=reverse(name,args=args,kwargs=kwargs)
        if not self.request.GET:
            target_url = base_url
        # 跳转添加按键前记住原搜索条件
        else:
            query_params = self.request.GET.urlencode()  # page=2&age=19
            # print(query_params)
            new_query_dict = QueryDict(mutable=True)
            new_query_dict['_filter'] = query_params
            target_url = "{}?{}".format(base_url, new_query_dict.urlencode())
        return target_url

    def reverse_add_url(self, *args, **kwargs):
        # 生成带搜索条件的添加URL
        return self.reverse_common(self.get_add_url_name, *args, **kwargs)

    def reverse_change_url(self, *args, **kwargs):
        # 生成带搜索条件的编辑URL
        return self.reverse_common(self.get_edit_url_name, *args, **kwargs)

    def reverse_del_url(self, *args, **kwargs):
        # 生成带搜索条件的删除URL
        return self.reverse_common(self.get_del_url_name, *args, **kwargs)

    def reverse_list_url(self):
        # 跳转回列表时，生成的URL
        name = '{}:{}'.format(self.site.namespace, self.get_list_url_name)
        base_url = reverse(name)
        param =self.request.GET.get('_filter')
        # print(param)
        if not param:
            return base_url
        # 跳转添加按键前记住原搜索条件
        list_url = "{}?{}".format(base_url, param)
        return list_url

    def get_add_btn(self):
        #子类可继承函数，预留此钩子函数
        if self.has_add_btn:
            return "<a class='btn btn-success' href='{}'>添加</a>".format(self.reverse_add_url())
        return None

    def get_model_form_class(self):
        #如果用户类自己有model_form_class ,否则用默认的
        if self.model_form_class:
            return self.model_form_class

        class DynamicModelForm(BootStrapModelForm):
            class Meta:
                model = self.model_class
                fields = '__all__'

        return DynamicModelForm

    def save(self,form,is_update=False):
        # 可被子类继承改写，保留自自定义的属性
        # if is_update:
        #     pass
        # else:
            form.save()

    def add_view(self, request,*args,**kwargs):
        # print(self.model_class,self.model_name,self.app_label)
        #请求进来后，先创建一个modelform
        model_form_class = self.get_model_form_class()
        if request.method == "POST":
            form = model_form_class(data=request.POST)
            if form.is_valid():
                # form.instance.depart_id=1
                self.save(form,is_update=False)
                #数据添加成功之后,跳转回列表页面，并带着原搜索条件
                return redirect(self.reverse_list_url())
            else:
                return render(request, 'stark/change.html', {'verpose_name': self.verpose_name, 'form': form})

        form = model_form_class()
        return render(request,'stark/change.html',{'verpose_name': self.verpose_name,'form':form})

    def change_view(self, request, pk,*args,**kwargs):
        object = get_object_or_404(self.model_class,pk=pk)
        model_form_class = self.get_model_form_class()
        if request.method == "POST":
            form = model_form_class(data=request.POST,instance=object)
            if form.is_valid():
                self.save(form,is_update=True)
                return redirect(self.reverse_list_url())

        form=model_form_class(instance=object)
        # print(object)
        return render(request,'stark/change.html',{'verpose_name': self.verpose_name,'form':form})

    def delete_view(self, request, pk,*args,**kwargs):

        origin_url = self.reverse_list_url()
        if request.method == "GET":
            return render(request, 'stark/delete.html', {'cancel': origin_url})
        object = get_object_or_404(self.model_class, pk=pk)
        object.delete()
        return redirect(origin_url)

    def get_url_name(self,param):
        if self.prev:
            return "{}_{}_{}_{}".format(self.app_label, self.prev, self.model_name, param)
        else:
            return "{}_{}_{}".format(self.app_label, self.model_name, param)

    @property
    def get_list_url_name(self):
        return self.get_url_name('list')

    @property
    def get_add_url_name(self):
        return self.get_url_name('add')

    @property
    def get_edit_url_name(self):
        return self.get_url_name('edit')

    @property
    def get_del_url_name(self):
        return self.get_url_name('delete')

    def get_urls(self):
        #若子类继承，可修改返回列表值
        # print(self.get_add_url_name,self.get_list_url_name,self.get_edit_url_name,self.get_del_url_name)
        patterns=[]
        #原始方法
        # patterns.append(path('list/',self.list_view,name="{}_{}_list".format(self.app_label, self.model_name)))
        # patterns.append(path('add/', self.add_view, name="{}_{}_add".format(self.app_label, self.model_name)))
        # patterns.append(path('edit/<int:pk>/', self.change_view, name="{}_{}_edit".format(self.app_label, self.model_name)))
        # patterns.append(path('delete/<int:pk>/', self.delete_view, name="{}_{}_delete".format(self.app_label, self.model_name)))
        patterns.append(path('list/',self.wapper(self.list_view),name=self.get_list_url_name))
        patterns.append(path('add/', self.wapper(self.add_view), name=self.get_add_url_name))
        patterns.append(path('edit/<int:pk>/', self.wapper(self.change_view), name=self.get_edit_url_name))
        patterns.append(path('delete/<int:pk>/', self.wapper(self.delete_view), name=self.get_del_url_name))
        # print(self.extra_urls())
        patterns.extend(self.extra_urls())
        # print(patterns)
        return patterns

    def extra_urls(self):
        return []

    def wapper(self,func):
        #保留原函数相关信息
        @wraps(func)
        #通过闭包函数，给常用的view添加 self.request=request
        def inner(request,*args,**kwargs):
            self.request=request
            return func(request,*args,**kwargs)
        return inner


class StarkSite():
    def __init__(self):
        self._registry = []
        self.app_name = 'stark'
        self.namespace = 'stark'

    def register(self,model_class,handler_class=None,prev=None):
        """
        :param model_class: models中的数据库相关的类
        :param handler_class: 处理请求的视图所在的类
        :param prev: 生成url前缀
        :return:
        """
        #handler_class()类的实例化
        if not handler_class:
            #设置默认的handler
            handler_class=StarkHandler

        self._registry.append({'model_class':model_class,'handler':handler_class(self,model_class,prev),'prev':prev})

        """
        self._registry = [
            {'model_class':models.Depart,'handler':DepartHandler(starksite_instance,models.Depart,prev)},
            {'model_class':models.UserInfo,'handler':UserInfoHandler(starksite_instance,models.UserInfo,prev)}
            {'model_class':models.Host,'handler': HostHandler(starksite_instance,models.Host,prev) }
        ]
        """
    @property
    def urls(self):
        # print(self.get_urls())
        return self.get_urls(),self.app_name,self.namespace#参照include的返回值 ，返回相似的内容

    def get_urls(self):
        patterns=[]
        # patterns demo
        # patterns.append(path('app01/',lambda request: HttpResponse('hello app01')))
        # patterns.append(path('app02/', lambda request: HttpResponse('hello app02')))
        for item in self._registry:
            # print(item['model_class'],item['handler'])
            # 打印 app name model name
            # print(model_class._meta.__dict__)
            # print(model_class,model_class._meta.app_label,model_class._meta.model_name)
            model_class = item['model_class']
            handler = item['handler']
            prev = item['prev']
            app_label = model_class._meta.app_label
            model_name = model_class._meta.model_name
            # print(app_label,type(app_label))
            if prev:
                #如果有前缀
                # patterns.append(path('{}/{}/{}/list/'.format(app_label, model_name, prev),
                #                      handler.list_view,
                #                      name="{}_{}_{}_list".format(app_label, model_name, prev)))
                # patterns.append(path('{}/{}/{}/add/'.format(app_label, model_name, prev),
                #                      handler.add_view,
                #                      name="{}_{}_{}_add".format(app_label, model_name, prev)))
                # patterns.append(path('{}/{}/{}/edit/<int:pk>/'.format(app_label, model_name, prev),
                #                      handler.change_view,
                #                      name="{}_{}_{}_edit".format(app_label, model_name, prev)))
                # patterns.append(path('{}/{}/{}/delete/<int:pk>/'.format(app_label, model_name, prev),
                #                      handler.delete_view,
                #                      name="{}_{}_{}_del".format(app_label, model_name, prev)))
                #下一级路由分发
                patterns.append(path('{}/{}/{}/'.format(app_label, prev, model_name), (handler.get_urls(), None, None)))

                # patterns.append(path('{}/{}/{}/'.format(app_label, prev, model_name),
                #                      include((handler.get_urls(),app_label),namespace="{}_{}".format(app_label,prev))))
            else:
                # patterns.append(path('{}/{}/list/'.format(app_label,model_name),
                #                      handler.list_view,
                #                      name="{}_{}_list".format(app_label,model_name)))
                # patterns.append(path('{}/{}/add/'.format(app_label, model_name),
                #                      handler.add_view,
                #                      name="{}_{}_add".format(app_label,model_name)))
                # patterns.append(path('{}/{}/edit/<int:pk>/'.format(app_label, model_name),
                #                      handler.change_view,
                #                      name="{}_{}_edit".format(app_label,model_name)))
                # patterns.append(path('{}/{}/delete/<int:pk>/'.format(app_label, model_name),
                #                      handler.delete_view,
                #                      name="{}_{}_del".format(app_label,model_name)))
                #分发的路由，namespace还无法定义
                patterns.append(path('{}/{}/'.format(app_label,model_name),(handler.get_urls(),None,None)))
                #使用include可以多级分发路由了
                # patterns.append(path('{}/{}/'.format(app_label, model_name),
                #                 include((handler.get_urls(),app_label),namespace=app_label)))
        return patterns


site = StarkSite()  # 单例模式
